/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.hamacekj.geometr;

import cz.hamacekj.geometr.plugin.ConstructionObject;
import cz.hamacekj.geometr.plugin.ConstructionPlugin;
import cz.hamacekj.geometr.plugin.ICircleObject;
import cz.hamacekj.geometr.plugin.ILineObject;
import cz.hamacekj.geometr.plugin.IPoint;
import cz.hamacekj.geometr.plugin.IPointObject;
import cz.hamacekj.geometr.plugin.IRayObject;
import cz.hamacekj.geometr.plugin.ISegmentObject;
import cz.hamacekj.geometr.plugin.IToolBox;
import cz.hamacekj.geometr.plugin.InputParametersInterface;
import cz.hamacekj.geometr.plugin.PrimitiveObject;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Jeden krok v postupu konstrukce.
 * @author hamacekh
 */
public class Step {
    
    /** Krok není viditelný v nákresu */
    public final static int HIDDEN = 0;
    /** Krok je v nákresu viditelný */
    public final static int VISIBLE = 1;
    /** Krok je v nákresu viditelný a je nakreslený silnější čarou */
    public final static int STRONG = 2;
    
    public Step(ConstructionObject[] objects, InputParametersInterface params, ConstructionPlugin createdBy) {
        this.objects = objects;
        this.params = params;
        this.createdBy = createdBy;
    }
 
    
    /**
     * Vrátí všechny objekty, ze kterých je složen tento krok.
     * @return Pole objektů, ze kterých je složen tento krok.
     */
    public ConstructionObject[] getConstructionObjects(){
        return this.objects;
    }
    
    /**
     * Vrátí vstupní parametry, pomocí kterých byl tento krok vytvořen.
     * @return vstupní parametry.
     */
    public InputParametersInterface getInputParameters(){
        return this.params;
    }
    
    /**
     * Vrátí plugin, pomocí kterého byl tento objekt vytvořen.
     * Vrátí null pokud není známo pomocí kterého pluginy byl vytvořen (načtení souboru).
     * @return Plugin, pomocí kterého byl tento vytvořen.
     */
    public ConstructionPlugin getCreatedBy(){
        return this.createdBy;
    }
    
    /**
     * Vrátí viditelnost tohoto kroku v nákresu. Je jednou z hodnot:
     * <ul>
     * <li> {@link #HIDDEN} objekt není v nákresu vidět
     * <li> {@link #VISIBLE} objekt je v nákresu vidět
     * <li> {@link #STRONG} objekt je v nákresu vidět zvýrazněný
     * </ul>
     * V popisu konstrukce je krok vidět vždy.
     * @return Viditelnost objektu
     */
    public int getVisibility(){
        return this.visibility;
    }
    
    /**
     * Nastaví viditelnost tohoto kroku v nákresu. Je jednou z hodnot:
     * <ul>
     * <li> {@link #HIDDEN} objekt není v nákresu vidět
     * <li> {@link #VISIBLE} objekt je v nákresu vidět
     * <li> {@link #STRONG} objekt je v nákresu vidět zvýrazněný
     * </ul>
     * V popisu konstrukce je krok vidět vždy.
     * @param visibility Viditelnost objektu
     */
    public void setVisibility(int visibility){
        this.visibility = visibility;
    }
    
    /**
     * Vrátí ConstructionObject, který má jméno name nebo null pokud takový nemá.
     * @param name Jméno hledaného objektu.
     * @return Hledaný objekt nebo null.
     */
    public ConstructionObject getObjectByName(String name){
        for(ConstructionObject o: objects){
            if(name.equals(o.getLabel())){
                return o;
            }
        }
        return null;
    }
    
    /**
     * Vrátí ConstructionObject, který má jméno name a je jednoho z typů type.
     * Vrátí null, pokud takový objekt není.
     * @param name Jméno hledaného objektu.
     * @param type Pole možných názvů typů hledaného objektu.
     * @return Hledaný objekt nebo null.
     */
    public ConstructionObject getObjectByName(String name, String[] type){
        boolean correctType = false;
        for(String t : type){
            if(t.equals(this.getCreatedBy().getPluginName())){
                correctType = true;
            }
        }
        if(!correctType){
            return null;
        }
        for(ConstructionObject o: objects){
            if(name.equals(o.getLabel())){
                return o;
            }
        }
        return null;
    }
    
    @Override
    public String toString(){
        return this.getConstructionObjects()[0].getStepDescription();
    }
    
    /**
     * Vytvoří krok z dat v elementu.
     * @param el element ze kterého budu brát data.
     * @param plugins načtené pluginy. Budou se v nich hledat createdBy a createdByParams.
     * @return Vytvořený krok.
     */
     public static Step createFromDOMElement(Element el, Map<String, ConstructionPlugin> plugins){
         IToolBox toolbox = new ToolBox();
         String createdByName = el.getAttribute("createdBy");
         String createdByParamsName = el.getAttribute("createdByParams");
         int visibility = new Integer(el.getAttribute("visibility"));
         final ConstructionPlugin createdBy = plugins.get(createdByName);
         InputParametersInterface createdByParamsTemp = null;
         if(createdBy != null){
             for(InputParametersInterface p : createdBy.getPossibleParameters()){
                 if(createdByParamsName.equals(p.getParametersCaption())){
                     createdByParamsTemp = p;
                     break;
                 }
             }
         }
         final InputParametersInterface createdByParams = createdByParamsTemp;
         ArrayList<ConstructionObject> constructionObjects = new ArrayList<>();
         NodeList list = el.getChildNodes();
         for(int i = 0; i < list.getLength(); i++){
             if(list.item(i).getNodeType() != Node.ELEMENT_NODE || 
                     !"constructionObject".equals(list.item(i).getNodeName())){
                 continue;
             }
             Element objectEl = (Element)list.item(i);
             final String objectLabel = objectEl.getAttribute("label");
             final String objectDescription = objectEl.getAttribute("stepDescription");
             final boolean objectIsLabelShown = "true".equals(objectEl.getAttribute("isLabelShown"));
             final NodeList primitiveObjectsList = objectEl.getElementsByTagName("primitiveObject");
             final Set<PrimitiveObject> primitiveObjects = new HashSet<>();
             for(int j = 0; j < primitiveObjectsList.getLength(); j++){
                 Element primObj = (Element)primitiveObjectsList.item(j);
                 switch (primObj.getAttribute("name")){
                    case "IPointObject":
                    {
                        NodeList points = primObj.getElementsByTagName("point");
                        Element point = (Element)points.item(0);
                        IPoint p = getIPointFromDOMElement((Element)points.item(0));
                        IPointObject po = toolbox.createPointObject(p);
                        primitiveObjects.add(po);
                        break;
                    }
                    case "ISegmentObject":
                    {
                        NodeList points = primObj.getElementsByTagName("point");
                        IPoint A = getIPointFromDOMElement((Element)points.item(0));
                        IPoint B = getIPointFromDOMElement((Element)points.item(1));
                        primitiveObjects.add(toolbox.createSegmentObject(A, B));
                        break;
                    }
                    case "IRayObject":
                    {
                        NodeList points = primObj.getElementsByTagName("point");
                        IPoint A = getIPointFromDOMElement((Element)points.item(0));
                        IPoint B = getIPointFromDOMElement((Element)points.item(1));
                        primitiveObjects.add(toolbox.createRayObject(A, B));
                        break;
                    }
                    case "ILineObject":
                    {
                        NodeList points = primObj.getElementsByTagName("point");
                        IPoint A = getIPointFromDOMElement((Element)points.item(0));
                        IPoint B = getIPointFromDOMElement((Element)points.item(1));
                        primitiveObjects.add(toolbox.createSegmentObject(A, B));
                        break;
                    }
                    case "ICircleObject":
                    {
                        NodeList points = primObj.getElementsByTagName("point");
                        IPoint S = getIPointFromDOMElement((Element)points.item(0));
                        double radius = new Double(primObj.getAttribute("radius"));
                        primitiveObjects.add(toolbox.createCircleObject(S, radius));
                        break;
                    }
                }
             }
             ConstructionObject obj = new ConstructionObject() {

                 @Override
                 public String getLabel() {
                     return objectLabel;
                 }

                 @Override
                 public boolean isLabelShown() {
                     return objectIsLabelShown;
                 }

                 @Override
                 public Set<PrimitiveObject> getPrimitiveObjects() {
                     return primitiveObjects;
                 }

                 @Override
                 public String getStepDescription() {
                     return objectDescription;
                 }
             };
             constructionObjects.add(obj);
         }
         Step s = new Step(constructionObjects.toArray(new ConstructionObject[1]),
                 createdByParams, createdBy);
         s.setVisibility(visibility);
         return s;
    }
    
    /**
     * Vytvoří DOM element obsahující všechny objekty a parametry
     * tohoto kroku nutné k jeho načtení.
     * @param doc
     * @return Dom element tohoto kroku.
     */
    public Element createDOMElement(Document doc){
        Element step = doc.createElement("step");
        step.setAttribute("createdBy", createdBy.getPluginName());
        step.setAttribute("createdByParams", params.getParametersCaption());
        step.setAttribute("visibility", new Integer(visibility).toString());
        for(ConstructionObject o : objects){
            Element object = doc.createElement("constructionObject");
            object.setAttribute("label", o.getLabel());
            object.setAttribute("stepDescription", o.getStepDescription());
            object.setAttribute("isLabelShown", o.isLabelShown() ? "true" : "false");
            for(PrimitiveObject prim : o.getPrimitiveObjects()){
                Element primitiveElement = doc.createElement("primitiveObject");
                if(prim instanceof IPointObject){
                    IPointObject obj = (IPointObject)prim;
                    primitiveElement.setAttribute("name", "IPointObject");
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, obj.getPoint()));
                }
                if(prim instanceof ISegmentObject){
                    ISegmentObject seg = (ISegmentObject)prim;
                    primitiveElement.setAttribute("name", "ISegmentObject");
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, seg.getA(), "A"));
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, seg.getB(), "B"));
                }
                if(prim instanceof IRayObject){
                    IRayObject ray = (IRayObject)prim;
                    primitiveElement.setAttribute("name", "IRayObject");
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, ray.getA(), "A"));
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, ray.getB(), "B"));
                }
                if(prim instanceof ILineObject){
                    ILineObject line = (ILineObject)prim;
                    primitiveElement.setAttribute("name", "ILineObject");
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, line.getA(), "A"));
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, line.getB(), "B"));
                }
                if(prim instanceof ICircleObject){
                    ICircleObject circ = (ICircleObject)prim;
                    primitiveElement.setAttribute("name", "ICircleObject");
                    primitiveElement.setAttribute("radius",
                            new Double(circ.getRadius()).toString());
                    primitiveElement.appendChild(
                            getIPointDOMElement(doc, circ.getCenter(), "center"));
                }
                object.appendChild(primitiveElement);
            }
            step.appendChild(object);
        }
        return step;
    }
    
    private Element getIPointDOMElement(Document doc, IPoint p){
        return getIPointDOMElement(doc, p, null);
    }
    
    private Element getIPointDOMElement(Document doc, IPoint p, String name){
        Element elem = doc.createElement("point");
        if(name != null){
            elem.setAttribute("name", name);
        }
        elem.setAttribute("x", new Double(p.getX()).toString());
        elem.setAttribute("y", new Double(p.getY()).toString());
        return elem;
    }
    
    private static IPoint getIPointFromDOMElement(Element el){
        if(!"point".equals(el.getNodeName())){
            return null;
        }
        final double x = new Double(el.getAttribute("x"));
        final double y = new Double(el.getAttribute("y"));
        return new Point(x, y);
    }
    
    private ConstructionObject[] objects;
    private InputParametersInterface params;
    private ConstructionPlugin createdBy;
    private int visibility = Step.VISIBLE;
}
